// 14 重载运算与类型转换
/**
 * 当运算符被用于类类型的对象时，C++语言允许我们为其指定新的含义；同时，我们也能自定义类类型之间的转换规则。和内置类型的转换一样，类类型转换隐式地将一种类型的对象转换成另一种我们所需类型的对象。
 * 当运算符作用于类类型的运算对象时，可以通过运算符重载重新定义该运算符的含义。明智地使用运算符重载能令我们的程序更易于编写和阅读。
 */

#include <iterator>
#include <vector>
#include <list>
#include <deque>
#include <forward_list>
#include <string>
#include <array>
#include <stack>
#include <queue>
#include <algorithm>
#include <numeric>
using std::swap;
using std::vector, std::list, std::deque, std::forward_list, std::string, std::array, std::stack, std::queue;
#include "../Chapter07/Sales_data.h"
#include <iostream>
using std::begin, std::cbegin, std::end, std::cend, std::find, std::accumulate, std::equal, std::fill, std::fill_n, std::back_inserter;
using std::cin, std::cout, std::endl;
using std::copy, std::replace, std::replace_copy;
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <memory>
#include <new>
using namespace std;
#include "../Chapter13/13.5.cc" // 不能编译，因为重复定义的main函数
#include "../Chapter12/12.1.6.cc" // 不能编译，因为重复定义的main函数
#include <functional>

// 这个类只定义了一种操作：函数调用运算符，它负责接受一个int类型的实参，然后返回该实参的绝对值。
struct absInt
{
    int operator()(int val) const
    {
        return val < 0 ? -val : val;
    }
};

class PrintString
{
public:
    PrintString(ostream &o=cout, char c=' ') : os(o), sep(c) { }
    void operator()(const string &s) const { os << s << sep; }
private:
    ostream &os; // 用于写入的目的流
    char sep;    // 用于将不同输出隔开的字符
};

class ShorterString
{
public:
    bool operator()(const string &s1, const string &s2) const
    {
        return s1.size() < s2.size();
    }
};

// 普通函数
int add(int i,int j) {return i + j;}
// lambda，其产生一个未命名的函数对象类
auto mod = [](int i,int j) { return i % j; }; // 注意有个分号
// 函数对象
struct divide
{
    int operator()(int denominator, int divisor)
    {
        return denominator / divisor;
    }
};

int main()
{
    // 14.8 函数调用运算符
    // 如果类重载了函数调用运算符，则我们可以像使用函数一样使用该类的对象。因为这样的类同时也能存储状态，所以与普通函数相比它们更加灵活。
    int i = -42;
    absInt absObj;      // 含有调用运算符的对象
    int ui = absObj(i); // 将i传递给absObj.operator()
    // 函数调用运算符必须是成员函数。一个类可以定义多个不同版本的调用运算符，相互之间应该在参数数量或类型上有所区别。
    // 如果类定义了调用运算符，则该类的对象称作函数对象（function object）。因为可以调用这种对象，所以我们说这些对象的“行为像函数一样”。

    // 含有状态的函数对象类
    // 和其他类一样，函数对象类除了operator（）之外也可以包含其他成员。函数对象类通常含有一些数据成员，这些成员被用于定制调用运算符中的操作。
    PrintString printer; // 使用默认值，打印到cout
    printer("s");        // 在cout中打印s，后面跟一个空格
    PrintString errors(cerr, '\n');
    errors("s");         // 在cerr中打印s，后面跟一个换行符
    // 函数对象常常作为泛型算法的实参。例如，可以使用标准库for_each算法（参见10.3.2节，第348页）和我们自己的PrintString类来打印容器的内容：
    vector<string> vs = { "wow", "hi", "cgs", "die" };
    for_each(vs.begin(), vs.end(), PrintString(cerr, '\n'));
    // for_each的第三个实参是类型PrintString的一个临时对象，其中我们用cerr和换行符初始化了该对象。
    // 当程序调用for_each时，将会把vs中的每个元素依次打印到cerr中，元素之间以换行符分隔。

    // 14.8.1 lambda是函数对象
    // 在前一节中，我们使用一个PrintString对象作为调用for_each的实参，这一用法类似于我们在10.3.2节（第346页）中编写的使用lambda表达式的程序。
    // 当我们编写了一个lambda后，编译器将该表达式翻译成一个未命名类的未命名对象（参见10.3.3节，第349页）。
    // 在lambda表达式产生的类中含有一个重载的函数调用运算符。
    vector<string> words = { "jojo", "cgs", "hahahaha", "en", "okay" };
    // 根据单词长度对其进行排序，对于长度相同的单词按照字母表顺序排序
    stable_sort(words.begin(), words.end(), [](const string &a, const string &b){ return a.size() < b.size(); }); // 该lambda表达式，其行为类似上面的ShorterString类的一个未命名对象
    // 如我们在10.3.3节（第352页）所见，默认情况下lambda不能改变它捕获的变量。
    // 因此在默认情况下，由lambda产生的类当中的函数调用运算符是一个const成员函数。如果lambda被声明为可变的，则调用运算符就不是const的了。

    // 表示lambda及相应捕获行为的类
    // 如我们所知，当一个lambda表达式通过引用捕获变量时，将由程序负责确保lambda执行时引用所引的对象确实存在（参见10.3.3节，第350页）。
    // 因此，编译器可以直接使用该引用而无须在lambda产生的类中将其存储为数据成员。
    // 相反，通过值捕获的变量被拷贝到lambda中（参见10.3.3节，第350页）。
    // 因此，这种lambda产生的类必须为每个值捕获的变量建立对应的数据成员，同时创建构造函数，令其使用捕获的变量的值来初始化数据成员。
    // lambda表达式产生的类不含默认构造函数、赋值运算符及默认析构函数；它是否含有默认的拷贝/移动构造函数则通常要视捕获的数据成员类型而定（参见13.1.6节，第450页和13.6.2节，第475页）。

    // 14.8.2 标准库定义的函数对象
    // 标准库定义了一组表示算术运算符、关系运算符和逻辑运算符的类，每个类分别定义了一个执行命名操作的调用运算符。
    // 例如，plus类定义了一个函数调用运算符用于对一对运算对象执行+的操作；modulus类定义了一个调用运算符执行二元的%操作；equal_to类执行==，等等。
    // 这些类都被定义成模板的形式，我们可以为其指定具体的应用类型，这里的类型即调用运算符的形参类型。例如，plus<string>令string加法运算符作用于string对象；plus<int>的运算对象是int；
    plus<int> intAdd;      // 可执行int加法的函数对象
    negate<int> intNegate; // 可对int值取反的函数对象
    int sum = intAdd(10,20);        // 使用intAdd::operator()(int,int)求10和20的和
    sum = intNegate(intAdd(10,20)); // 
    // 表14.2所列的类型定义在functional头文件中。

    // 14.8.3 可调用对象与function
    // C++语言中有几种可调用的对象：函数、函数指针、lambda表达式（参见10.3.2节，第346页）、bind创建的对象（参见10.3.4节，第354页）以及重载了函数调用运算符的类。
    // 和其他对象一样，可调用的对象也有类型。例如，每个lambda有它自己唯一的（未命名）类类型；函数及函数指针的类型则由其返回值类型和实参类型决定，等等。
    // 然而，两个不同类型的可调用对象却可能共享同一种调用形式（call signature）。调用形式指明了调用返回的类型以及传递给调用的实参类型。
    // 一种调用形式对应一个函数类型，例如：
    //int(int,int) 是一个函数类型，它接受两个int、返回一个int。

    // 不同类型可能具有相同的调用形式
    // 对于几个可调用对象共享同一种调用形式的情况，有时我们会希望把它们看成具有相同的类型。
    // 上面这些可调用对象（指上面定义的add、mod和divide）分别对其参数执行了不同的算术运算，尽管它们的类型各不相同，但是共享同一种调用形式：int(int,int)
    // 一个函数表（function table）用于存储指向这些可调用对象的“指针”。当程序需要执行某个特定的操作时，从表中查找该调用的函数。
    // 在C++语言中，函数表很容易通过map来实现。
    map<string, int(*)(int,int)> binops; // 构建从运算符到函数指针的映射关系，其中函数接受两个int，返回一个int。int(*)(int,int)为函数指针。
    binops.insert({"+",add}); // 正确，add是一个指向正确类型函数的指针。{"+",add}是一个pair（参见11.2.3节，379页）
    //binops.insert({"%",mod}); // 错误，mod不是一个函数指针
    // 问题在于mod是个lambda表达式，而每个lambda有它自己的类类型，该类型与存储在binops中的值的类型不匹配。

    // 标准库function类型
    // 我们可以使用一个名为function的新的标准库类型解决上述问题，function定义在functional头文件中，表14.3列举出了function定义的操作。
    // function是一个模板，和我们使用过的其他模板一样，当创建一个具体的function类型时我们必须提供额外的信息。
    // 在此例中，所谓额外的信息是指该function类型能够表示的对象的调用形式。参考其他模板，我们在一对尖括号内指定类型：
    //function<int(int,int)> f1 = add; // 函数指针
    function<int(int,int)> f2 = divide(); // 函数对象类的对象
    function<int(int,int)> f3 = [](int i,int j) {return i * j;}; // lambda
    cout << f2(4,2) << endl; 
    cout << f3(4,2) << endl;
    // 使用这个function类型我们可以重新定义map：    
    // 我们能把所有可调用对象，包括函数指针、lambda或者函数对象在内，都添加到这个map中：
    map<string,function<int(int,int)>> binops1;
    binops1.insert({"%",mod}); // 正确
    // 一如往常，当我们索引map时将得到关联值的一个引用。如果我们索引binops，将得到function对象的引用。
    // function类型重载了调用运算符，该运算符接受它自己的实参然后将其传递给存好的可调用对象：
    binops1["%"](10,5); // 调用lambda函数对象

    // 重载的函数与function
    // 我们不能（直接）将重载函数的名字存入function类型的对象中：
    //function<int(int,int)> f1 = add; // 函数指针。目前是错误的，因为Sales_data中有add的定义，这里也有。编译器不知用哪个add。
    // 解决上述二义性问题的一条途径是存储函数指针（参见6.7节，第221页）而非函数的名字：
    int (*fp)(int,int) = add; // 指针所指的add是接受两个int的版本
    binops1.insert({"+",fp}); // 正确，fp指向一个正确的add版本
    // 同样，我们也能使用lambda来消除二义性：
    binops1.insert({"+",[](int i,int j){return add(i,j);}});
    // 新版本标准库中的function类与旧版本中的unary_function和binary_function没有关联，后两个类已经被更通用的bind函数替代了（参见10.3.4节，第357页）。


    return 0;
}